/**
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { UseColumn, UseDataView, UseFilter, UseGroupData, UseSelection } from './types';
import { computed, ref } from 'vue';
import { DataGridProps } from '../data-grid.props';

export interface DataViewFilter {
    field: string;
    fieldName: string;
    fieldType: string;
    filterValue: string;
    filter: (dataItem: any) => boolean;
}
export interface DataViewSorter {
    field: string;
    compare: (preItem: any, postItem: any) => number;
}

export function useDataView(
    props: DataGridProps,
    columnComposition: UseColumn,
    filterComposition: UseFilter,
    groupDataComposition: UseGroupData,
    selectionComposition: UseSelection
): UseDataView {
    const collapseMap = new Map<string, boolean>();
    const filterMap = new Map<string, DataViewFilter>();
    const groupFilterMap = new Map<string, DataViewFilter>();
    const sorterMap = new Map<string, DataViewSorter>();
    const filters = ref<DataViewFilter[]>([]);
    const sorters = ref<DataViewSorter[]>([]);
    const { columnContext } = columnComposition;
    const { getSelectionRow } = selectionComposition;
    const { generateGroupData, groupFields, shouldGroupingData } = groupDataComposition;
    const getNewDataItem = ref(props.newDataItem);
    const originalData = ref(props.data);
    const totalData = ref(props.data);
    const rawView = ref(props.data);
    const summaryOptions = ref(props.summary);
    const groupSummaryFields = computed(() => {
        const options = summaryOptions.value;
        return options?.groupFields || [];
    });
    const summaries = groupSummaryFields.value.reduce((sumaries: Map<string, number>, summaryField: string) => {
        sumaries.set(summaryField, 0);
        return sumaries;
    }, new Map<string, number>());

    const totalItems = computed<number>(() => {
        return totalData.value.length;
    });

    const pagination = ref(props.pagination);
    const pageSize = computed<number>(() => {
        return pagination.value?.size || totalData.value.length;
    });
    const pageIndex = ref(1);

    function resetDataView() {
        totalData.value = originalData.value;
        if (shouldGroupingData.value) {
            totalData.value = generateGroupData(groupFields.value, originalData.value, columnContext.value);
        }
        const startRowIndex = (pageIndex.value - 1) * pageSize.value;
        const endRowIndex = Math.min(startRowIndex + pageSize.value, totalItems.value);
        let rowIndex = 0;
        const dataViewItems = [];
        if (totalData.value.length) {
            for (let index = startRowIndex; index < endRowIndex; index++) {
                const dataItem = totalData.value[index] as any;
                if (dataItem.__fv_data_grid_group_row__) {
                    rowIndex = 0;
                    dataItem.__fv_data_index__ = '';
                } else if (dataItem.__fv_data_grid_group_summary__) {
                    dataItem.__fv_data_index__ = '';
                } else {
                    rowIndex++;
                    dataItem.__fv_data_index__ = rowIndex;
                }
                dataItem.__fv_index__ = index;
                groupSummaryFields.value.forEach((summaryField: string) => {
                    const summaryFieldValue = summaries.get(summaryField) || 0;
                    summaries.set(summaryField, summaryFieldValue + dataItem[summaryField]);
                });
                dataViewItems.push(dataItem);
            }
        }
        rawView.value = [...dataViewItems];
        return dataViewItems;
    }
    const dataView = ref(resetDataView());

    function updateDataView() {
        const latestData = resetDataView();
        dataView.value = latestData;
    }

    function applyFilterAndSorter(filters: DataViewFilter[], sorters: DataViewSorter[], reset = false) {
        const source = reset ? resetDataView() : [...rawView.value];
        const filteredData = source.filter((dataItem: any) => filterComposition.apply(dataItem));
        const groupedData = filters && filters.length ? filteredData
            .filter((dataItem: any) => {
                return filters.reduce((result: boolean, filter: DataViewFilter) => {
                    return result && filter.filter(dataItem);
                }, true);
            }) : filteredData;
        const sorteredData = sorters && sorters.length ?
            groupedData.sort((preItem: any, postItem: any) => {
                const multiSorter = [...sorters];
                const firstSorter = multiSorter.shift() as DataViewSorter;
                let sortResult = firstSorter.compare(preItem[firstSorter.field], postItem[firstSorter.field]);
                while (sortResult === 0 && multiSorter.length !== 0) {
                    const sorter = multiSorter.shift();
                    if (sorter) {
                        sortResult = sorter.compare(preItem[sorter.field], postItem[sorter.field]);
                    }
                }
                return sortResult;
            }) : groupedData;
        dataView.value = sorteredData
            .map((dataItem: any, index: number) => {
                if (dataItem.__fv_data_grid_group_row__) {
                    const groupedRowId = `group_of_${dataItem.__fv_data_grid_group_field__}_${dataItem.__fv_data_grid_group_value__}`;
                    dataItem.__fv_data_grid_group_collapse__ = !!collapseMap.get(groupedRowId);
                }
                dataItem.__fv_index__ = index;
                return dataItem;
            });
        return dataView.value;
    }

    function setSorters(latestSorters: DataViewSorter[]) {
        sorterMap.clear();
        sorters.value = latestSorters;
        sorters.value.reduce((result: Map<string, DataViewSorter>, sorter: DataViewSorter) => {
            result.set(sorter.field, sorter);
            return result;
        }, sorterMap);
        // return applyFilterAndSorter(Array.from(filterMap.values()), sorters.value);
        return applyFilterAndSorter([], sorters.value);
    }

    function addSorter(sortKey: string, sorter: DataViewSorter) {
        sorterMap.set(sortKey, sorter);
        sorters.value = Array.from(sorterMap.values());
        // return applyFilterAndSorter(Array.from(filterMap.values()), sorters.value);
        return applyFilterAndSorter([], sorters.value);
    }

    function removeSorter(sortKey: string) {
        sorterMap.delete(sortKey);
        sorters.value = Array.from(sorterMap.values());
        // return applyFilterAndSorter(Array.from(filterMap.values()), sorters.value);
        return applyFilterAndSorter([], sorters.value);
    }

    function addFilter(filterKey: string, filter: DataViewFilter) {
        filterMap.set(filterKey, filter);
        filters.value = Array.from(filterMap.values());
        // return applyFilterAndSorter(filters.value, Array.from(sorterMap.values()));
        return applyFilterAndSorter([], Array.from(sorterMap.values()));
    }

    function removeFilter(filterKey: string) {
        filterMap.delete(filterKey);
        filters.value = Array.from(filterMap.values());
        // return applyFilterAndSorter(filters.value, Array.from(sorterMap.values()), true);
        return applyFilterAndSorter([], Array.from(sorterMap.values()), true);
    }

    function removeAllFilter() {
        filterMap.clear();
        filters.value = [];
        // return applyFilterAndSorter(filters.value, Array.from(sorterMap.values()), true);
        return applyFilterAndSorter([], Array.from(sorterMap.values()), true);
    }

    function collapse(collapseField: string, collapseValue: any) {
        const groupedRowId = `group_of_${collapseField}_${collapseValue}`;
        collapseMap.set(groupedRowId, true);
        const collapseFieldFilter = (dataItem: any) => dataItem[collapseField] !== collapseValue;
        groupFilterMap.set(`collapse_${collapseField}_${collapseValue}`, {
            field: collapseField,
            fieldName: collapseField,
            fieldType: 'string',
            filterValue: collapseValue,
            filter: collapseFieldFilter
        });
        return applyFilterAndSorter(Array.from(groupFilterMap.values()), Array.from(sorterMap.values()));
    }

    function expand(expandField: string, expandValue: any) {
        const groupedRowId = `group_of_${expandField}_${expandValue}`;
        collapseMap.set(groupedRowId, false);
        groupFilterMap.delete(`collapse_${expandField}_${expandValue}`);
        return applyFilterAndSorter(Array.from(groupFilterMap.values()), Array.from(sorterMap.values()), true);
    }

    function getRange(filters: DataViewFilter[], start: number, end: number) {
        const filteredData =
            filters && filters.length
                ? dataView.value.filter((dataItem: any, index: number) => {
                    return filters.reduce((result: boolean, filter: DataViewFilter) => {
                        return result && filter.filter(dataItem);
                    }, true);
                })
                : dataView.value;
        return filteredData.slice(start, end);
    }

    function navigatePageTo(targetPageIndex: number) {
        const shouldNavigating = targetPageIndex * pageSize.value <= totalItems.value;
        if (shouldNavigating) {
            pageIndex.value = targetPageIndex;
            updateDataView();
        }
    }

    function insertNewDataItem() {
        const newDataItem = getNewDataItem.value();
        const currentSelectionRow = getSelectionRow();
        const targetIndex = currentSelectionRow ? currentSelectionRow.dataIndex - 1 : 0;
        originalData.value.splice(targetIndex, 0, newDataItem);
        updateDataView();
    }

    function removeDataItem(dataIndex: number) {
        const targetIndex = dataIndex ? dataIndex - 1 : 0;
        originalData.value.splice(targetIndex, 1);
        updateDataView();
    }

    function recalculatePageIndexByPageSize(currentPageIndex: number, originalPageSize: number, newPageSize: number) {
        const currentVisibleRecordsCount = currentPageIndex * originalPageSize;
        const latestPageIndex = Math.ceil(currentVisibleRecordsCount / newPageSize);
        return latestPageIndex;
    }

    function refresh() {
        const collepseFilter = groupFilterMap.size > 0 ? Array.from(groupFilterMap.values()) : [];
        return applyFilterAndSorter(collepseFilter, Array.from(sorterMap.values()));
    }

    function changePageSizeTo(newPageSize: number) {
        if (pagination.value) {
            pagination.value.size = newPageSize;
            updateDataView();
        }
    }

    return {
        addFilter,
        addSorter,
        changePageSizeTo,
        collapse,
        dataView,
        expand,
        filters,
        getRange,
        navigatePageTo,
        summaries,
        totalItems,
        insertNewDataItem,
        removeAllFilter,
        removeDataItem,
        removeFilter,
        removeSorter,
        rawView,
        refresh,
        setSorters,
        sorters,
        updateDataView
    };
}
